package com.zhs.type;

import java.util.ArrayList;

import com.zhs.javacc.Node;
import com.zhs.javacc.SimpleNode;
import com.zhs.xyzsdk.editors.XYZEditor;

/**
 * One expression can be evaluated recursively.
 * 
 */
public class Expression {
	private Type type;
	private SimpleNode value;
	private Scope scope;

	public String visit() {
		return innerVisit(value);
	}

	private String innerVisit(SimpleNode node) {
		String current_exp = node.toString();
		if (current_exp.equals("And")) {
			return visitAnd(node);
		} else if (current_exp.equals("Compare")) {
			return visitCompare(node);
		} else if (current_exp.equals("Add")) {
			return visitAdd(node);
		} else if (current_exp.equals("Sub")) {
			return visitSub(node);
		} else if (current_exp.equals("Times")) {
			return visitTimes(node);
		} else if (current_exp.equals("ArrayLengthExpression")) {
			return visitLength(node);
		} else if (current_exp.equals("AllocationIntExpression")) {
			return visitIntArray(node);
		} else if (current_exp.equals("AllocationDoubleExpression")) {
			return visitDoubleArray(node);
		} else if (current_exp.equals("AllocationIDExpression")) {
			return visitIDAllocation(node);
		} else if (current_exp.equals("MethodInvokeExpression")) {
			return visitMethodInvoke(node);
		} else if (current_exp.equals("ArrayAccessExpression")) {
			return visitArrayAccess(node);
		} else if (current_exp.equals("ParenthesisExpression")) {
			return visitParenthesis(node);
		} else if (current_exp.equals("NoExpression")) {
			return visitNo(node);
		} else if (current_exp.equals("IntegerLiteral")) {
			return "int";
		} else if (current_exp.equals("DoubleLiteral")) {
			return "double";
		} else if (current_exp.equals("TrueLiteral")) {
			return "boolean";
		} else if (current_exp.equals("FalseLiteral")) {
			return "boolean";
		} else if (current_exp.equals("ThisExpression")) {
			return type.getTypeName();
		} else if (current_exp.equals("Identifier")) {
			Variable var = scope.lookupVariable(node.jjtGetFirstToken().image);
			if (var != null) {
				return var.getTypeName();
			} else {
				// no such variable
				issueError("Variable undeclared: ",
						node.jjtGetFirstToken().image, node);
			}
		}
		return "";
	}

	private String visitAnd(SimpleNode node) {
		for (int i = 0; i < node.jjtGetNumChildren(); i++) {
			String temp = innerVisit((SimpleNode) node.jjtGetChild(i));
			if (!temp.equals("boolean")) {
				issueError("Type mismatch: ", "boolean", temp, node
						.jjtGetChild(i));
				return "";
			}
		}
		return "boolean";
	}

	private String visitCompare(SimpleNode node) {
		/*
		 * if (node.jjtGetNumChildren() != 2) {
		 * issueError("Incorrect number for comparison: ", "" +
		 * node.jjtGetNumChildren(), node.jjtGetChild(0)); return ""; }
		 */
		String a1 = innerVisit((SimpleNode) node.jjtGetChild(0));
		String a2 = innerVisit((SimpleNode) node.jjtGetChild(1));
		if (!(a1.equals(a2) && (a1.equals("int") || a1.equals("double")))
				&& !(a1.equals("int") && a2.equals("double"))
				&& !(a1.equals("double") && a2.equals("int"))) {
			issueError("Type mismatch for comparison: ", "int or double", a1
					+ " and " + a2, node);
			return "";
		}
		return "boolean";
	}

	private String visitAdd(SimpleNode node) {
		String t = "";
		for (int i = 0; i < node.jjtGetNumChildren(); i++) {
			String temp = innerVisit((SimpleNode) node.jjtGetChild(i));
			if (temp.equals("int")) {
				if (!t.equals("double")) {
					t = "int";
				}
			} else if (temp.equals("double")) {
				t = "double";
			} else {
				issueError("Type mismatch: ", "int or double", temp, node
						.jjtGetChild(i));
				return "";
			}
		}
		return t;
	}

	private String visitSub(SimpleNode node) {
		String t = "";
		for (int i = 0; i < node.jjtGetNumChildren(); i++) {
			String temp = innerVisit((SimpleNode) node.jjtGetChild(i));
			if (temp.equals("int")) {
				if (!t.equals("double")) {
					t = "int";
				}
			} else if (temp.equals("double")) {
				t = "double";
			} else {
				issueError("Type mismatch: ", "int or double", temp, node
						.jjtGetChild(i));
				return "";
			}
		}
		return t;
	}

	private String visitTimes(SimpleNode node) {
		String t = "";
		for (int i = 0; i < node.jjtGetNumChildren(); i++) {
			String temp = innerVisit((SimpleNode) node.jjtGetChild(i));
			if (temp.equals("int")) {
				if (!t.equals("double")) {
					t = "int";
				}
			} else if (temp.equals("double")) {
				t = "double";
			} else {
				issueError("Type mismatch: ", "int or double", temp, node
						.jjtGetChild(i));
				return "";
			}
		}
		return t;
	}

	private String visitLength(SimpleNode node) {
		String exp = innerVisit((SimpleNode) node.jjtGetChild(0));
		if (!exp.equals("int[]") && !exp.equals("double[]")) {
			issueError("Type mismatch: ", "int or double arrays", exp, node
					.jjtGetChild(0));
			return "";
		}
		return "int";
	}

	private String visitIntArray(SimpleNode node) {
		String exp = innerVisit((SimpleNode) node.jjtGetChild(0));
		if (!exp.equals("int")) {
			issueError("Type mismatch: ", "int for indexing"
					+ " through an array", exp, node.jjtGetChild(0));
			return "";
		}
		return "int[]";
	}

	private String visitDoubleArray(SimpleNode node) {
		String exp = innerVisit((SimpleNode) node.jjtGetChild(0));
		if (!exp.equals("int")) {
			issueError("Type mismatch: ", "int for indexing"
					+ " through an array", exp, node.jjtGetChild(0));
			return "";
		}
		return "double[]";
	}

	private String visitIDAllocation(SimpleNode node) {
		String id = ((SimpleNode) node.jjtGetChild(0)).jjtGetFirstToken().image;
		Type t = Type.lookupType(id);
		if (t == null) {
			issueError("Allocating undefined type: ", id, node.jjtGetChild(0));
			return "";
		}
		return id;
	}

	private String visitMethodInvoke(SimpleNode node) {
		String exp = innerVisit((SimpleNode) node.jjtGetChild(0));
		String method = ((SimpleNode) node.jjtGetChild(1).jjtGetChild(0))
				.jjtGetFirstToken().image;
		if (Type.lookupType(exp) == null) {
			return "";
		}
		Method temp = Type.lookupType(exp).lookupMethod(method);
		if (temp == null) {
			issueError("Invoking undefined method: ", method,
					((SimpleNode) node.jjtGetChild(1).jjtGetChild(0)));
			return "";
		}
		ArrayList<Variable> list = (ArrayList<Variable>) temp.getParameters();
		SimpleNode mynode = (SimpleNode) node.jjtGetChild(1).jjtGetChild(1);
		if (mynode.jjtGetNumChildren() != list.size()) {
			issueError("Incorrect parameter numbers: ", list.size() + "",
					mynode.jjtGetNumChildren() + "", mynode);
			return "";
		}
		for (int i = 0; i < mynode.jjtGetNumChildren(); i++) {
			String parameter = innerVisit((SimpleNode) mynode.jjtGetChild(i));
			if (typePromotion(list.get(i).getTypeName(), parameter).length() == 0) {
				issueError("Type mismatch: ", list.get(i).getTypeName(),
						parameter, mynode.jjtGetChild(i));
				return "";
			}
		}
		return temp.getReturnTypeName();
	}

	private String visitArrayAccess(SimpleNode node) {
		if (node.jjtGetNumChildren() != 2) {
			issueError("Multi-dimension array unsupported: ", (node
					.jjtGetNumChildren() - 1)
					+ " dimensions", node);
			return "";
		}
		if (node.jjtGetChild(0).toString().equals("AllocationIntExpression")
				|| node.jjtGetChild(0).toString().equals(
						"AllocationDoubleExpression")) {
			// TODO issue an error
			return "";
		}
		String exp1 = innerVisit((SimpleNode) node.jjtGetChild(0));
		if (!exp1.equals("int[]") && !exp1.equals("double[]")) {
			issueError("Type mismatch: ", "int or double array reference",
					exp1, node);
			return "";
		}
		String exp2 = innerVisit((SimpleNode) node.jjtGetChild(1)
				.jjtGetChild(0));
		if (!exp2.equals("int")) {
			issueError("Type mismatch: ", "int to index through an array",
					exp2, node.jjtGetChild(1));
			return "";
		}
		if (exp1.equals("int[]")) {
			return "int";
		} else if (exp1.equals("double[]")) {
			return "double";
		}
		return "";
	}

	private String visitParenthesis(SimpleNode node) {
		return innerVisit((SimpleNode) node.jjtGetChild(0));
	}

	private String visitNo(SimpleNode node) {
		String temp = innerVisit((SimpleNode) node.jjtGetChild(0));
		if (!temp.equals("boolean")) {
			issueError("Type mismatch: ", "boolean type for logical negation",
					temp, node);
			return "";
		}
		return "boolean";
	}

	public Scope getScope() {
		return scope;
	}

	public void setScope(Scope scope) {
		this.scope = scope;
	}

	public Type getType() {
		return type;
	}

	public SimpleNode getValue() {
		return value;
	}

	public void setType(Type type) {
		this.type = type;
	}

	public void setValue(SimpleNode value) {
		this.value = value;
	}

	public void issueError(String message, String target, Node node) {
		XYZEditor.addMarker(message + target + " LINE: "
				+ ((SimpleNode) node).jjtGetFirstToken().beginLine
				+ " COLUMN: "
				+ ((SimpleNode) node).jjtGetFirstToken().beginColumn,
				((SimpleNode) node).jjtGetFirstToken().beginLine);
	}

	public void issueError(String message, String intention, String target,
			Node node) {
		if (target.length() == 0)
			return;
		XYZEditor.addMarker(message + "NEED: " + intention + " FOUND: "
				+ target + " LINE: "
				+ ((SimpleNode) node).jjtGetFirstToken().beginLine
				+ " COLUMN: "
				+ ((SimpleNode) node).jjtGetFirstToken().beginColumn,
				((SimpleNode) node).jjtGetFirstToken().beginLine);
	}

	private String typePromotion(String host, String guest) {
		if (host.equals(guest)) {
			return host;
		} else if (host.equals("double")) {
			if (guest.equals("int"))
				return host;
		} else {
			Type hostType = Type.lookupType(host);
			Type guestType = Type.lookupType(guest);
			if (hostType == null) {
				// TODO
				return "";
			}
			if (guestType == null) {
				// TODO
				return "";
			}
			String temp = guestType.getSuperType();
			while (temp != null) {
				if (temp.equals(host)) {
					return host;
				}
				Type tempType = Type.lookupType(temp);
				if (tempType == null) {
					// TODO
					return "";
				}
				temp = tempType.getSuperType();
			}
		}
		// TODO
		return "";
	}
}
